package com.vxtree.boot.drools.manager;

import com.vxtree.boot.drools.rule.DroolsRule;
import lombok.extern.slf4j.Slf4j;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.runtime.KieContainer;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * DroolsManager
 *
 * @author WangChen
 * Created on 2024/1/30
 * @since 1.0
 */
@Slf4j
@Component
public class DroolsManager implements DisposableBean {

    private final KieServices kieServices = KieServices.Factory.get();

    private final KieFileSystem kieFileSystem;

    private final KieContainer kieContainer;

    private KieModuleModel kieModuleModel;

    public DroolsManager(KieFileSystem kieFileSystem, KieContainer kieContainer) {
        this.kieFileSystem = kieFileSystem;
        this.kieContainer = kieContainer;
    }

    public void add(List<DroolsRule> rules) {
        if (null == rules || rules.isEmpty()) {
            return;
        }
        kieModuleModel = kieServices.newKieModuleModel();
        rules.stream().collect(Collectors.groupingBy(DroolsRule::getKieBaseName)).forEach((kieBaseName, droolsRules) -> {
            if (existsKieBase(kieBaseName)) {
                log.info("kieBaseName:[{}]已经存在", kieBaseName);
                return;
            }
            // 创建一个kbase
            KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName);
            // 不是默认的kieBase
            kieBaseModel.setDefault(false);
            kieBaseModel.setEventProcessingMode(EventProcessingOption.STREAM);
            droolsRules.forEach(droolsRule -> {
                // 设置该KieBase需要加载的包路径
                kieBaseModel.addPackage(droolsRule.getKiePackageName());
                // 设置kieSession
                kieBaseModel.newKieSessionModel("kb-session-" + kieBaseName)
                        // 不是默认session
                        .setDefault(false);
                loadRule(droolsRule);
                buildModule(kieModuleModel);
            });
        });
        build();
    }

    public void update(List<DroolsRule> rules) {
        if (null == rules || rules.isEmpty()) {
            return;
        }
        rules.stream().collect(Collectors.groupingBy(DroolsRule::getKieBaseName)).forEach((kieBaseName, droolsRules) -> {
            if (!existsKieBase(kieBaseName)) {
                log.info("kieBaseName:[{}]不存在", kieBaseName);
                return;
            }
            // 获取到已经存在的kbase对象
            KieBaseModel kieBaseModel = kieModuleModel.getKieBaseModels().get(kieBaseName);
            // 获取到packages
            List<String> packages = kieBaseModel.getPackages();
            droolsRules.forEach(droolsRule -> {
                if (packages.contains(droolsRule.getKiePackageName())) {
                    loadRule(droolsRule);
                } else {
                    kieBaseModel.addPackage(droolsRule.getKiePackageName());
                    log.info("kieBase:{}添加一个新的包:{}", kieBaseName, droolsRule.getKiePackageName());
                    loadRule(droolsRule);
                    buildModule(kieModuleModel);
                }
            });
        });
        build();
    }

    public void delete(DroolsRule droolsRule, String ruleName) {
        String kieBaseName = droolsRule.getKieBaseName();
        String packageName = droolsRule.getKiePackageName();
        if (existsKieBase(kieBaseName)) {
            KieBase kieBase = kieContainer.getKieBase(kieBaseName);
            kieBase.removeRule(packageName, ruleName);
            String file = getFilePath(droolsRule);
            kieFileSystem.delete(file);
            build();
            log.trace("删除kieBase:[{}]包:[{}]下的规则:[{}]", kieBaseName, packageName, ruleName);
        }
    }

    public boolean existsKieBase(String kieBaseName) {
        if (null == kieContainer) {
            return false;
        }
        Collection<String> kieBaseNames = kieContainer.getKieBaseNames();
        if (kieBaseNames.contains(kieBaseName)) {
            return true;
        }
        log.info("需要创建KieBase:[{}]", kieBaseName);
        return false;
    }

    private void build() {
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        // 通过KieBuilder构建KieModule下所有的KieBase
        kieBuilder.buildAll();
        // 获取构建过程中的结果
        Results results = kieBuilder.getResults();
        // 获取错误信息
        List<Message> messages = results.getMessages(Message.Level.ERROR);
        if (null != messages && !messages.isEmpty()) {
            for (Message message : messages) {
                log.error("==" + message.getText());
            }
            throw new RuntimeException("加载规则出现异常");
        }
        ((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieBuilder.getKieModule());
    }

    private void loadRule(DroolsRule droolsRule) {
        String file = getFilePath(droolsRule);
        log.trace("加载虚拟规则文件:{}", file);
        kieFileSystem.write(file, droolsRule.getRuleContent());
    }

    private void buildModule(KieModuleModel kieModuleModel) {
        String kmoduleXml = kieModuleModel.toXML();
        log.trace("加载kmodule.xml:[\n{}]", kmoduleXml);
        kieFileSystem.writeKModuleXML(kmoduleXml);
    }

    private String getFilePath(DroolsRule droolsRule) {
        return basePath + droolsRule.getKiePackageName() + "/" + droolsRule.getRuleId() + ".drl";
    }

    @Override
    public void destroy() throws Exception {
        if (null != kieContainer) {
            kieContainer.dispose();
        }
    }

    private static final String basePath = "src/main/resources/";
}
